//$Id: Logic.java 249 2010-11-13 02:15:54Z cr.carbon $

package builders;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;

/**
 * This is the facade class involved in UI + Game interactions
 * It controls the state of the game as well
 * 
 * @author Yuling
 * 
 */
public class Logic {

	/**
	 * Logic keeps track of gameState in order to maintain a gameState.
	 */
	private GameState gameState;
	private int MAX_FILE_SIZE = 30; // 30 lines of saved game variables at max
	private int RESOURCE_TYPE = Player.Resource.values().length;
	private int AINumber = 0; // for naming of AIs
	private String PARTITION_STRING = "*****"; // for separation within save game files
	private String setTileManagerPath = ""; // for unrandomisation of tiles
	private boolean isSet = false;  // for unrandomisation of tiles

	/**
	 * usage of enum (as "constants") to read save files
	 */
	private enum SAVED_GAME_LINES {
		GAME_MODE, PREV_GAME_MODE, OUTPUT, PREV_OUTPUT, DIFFICULTY_LEVEL, TILES_YOU_CAN_MOVE_TO, MOVEMENT_ACTIVITY_POINTS, BUILDINGS_YOU_CAN_BUILD, ALL_BUILDINGS_AVAILABLE_FOR_BUILDING, CURRENT_PLAYER, PLAYER_START_LINE
	}
	private enum PLAYER_GAME_LINES {
		NAME, TYPE, PLAYER_DETAILS, BUILDING, RESOURCES
	}
	private enum PLAYER_DETAILS {
		HOMEBASE, POINTS, PIECE_POSITION
	}
	/**
	 * this would create a new Logic with a gameState inside of it
	 */
	public Logic(){
		gameState = new GameState();
		ArrayList<Building> allBuildingsAvailableForBuilding = new ArrayList<Building>();
		for (Building b : gameState.getBuildingManager().getManager()){
			allBuildingsAvailableForBuilding.add(b); // ensures that GUI will always have buildings to get image from to display
		}
		gameState.setAllBuildingsAvailableForBuilding(allBuildingsAvailableForBuilding);
	}

	/**
	 * This allows Logic to be initialised for UI to use
	 */
	public GameState initialise(){
		gameState.setGameMode(GameState.GameMode.SPLASH_SCREEN);
		ArrayList<Building> allBuildingsAvailableForBuilding = new ArrayList<Building>();
		for (Building b : gameState.getBuildingManager().getManager()){
			allBuildingsAvailableForBuilding.add(b); // ensures that GUI will always have buildings to get image from to display
		}
		gameState.setAllBuildingsAvailableForBuilding(allBuildingsAvailableForBuilding);
		if (isSet){
			gameState.setTileManager(new TileManager(setTileManagerPath));
		}
		return gameState;
	}

	/**
	 * This takes care of all messages from UI. 
	 * 
	 * @param message	The gameMode that the UI is currently in/will want to change into
	 * @return			A new GameState for UI to use/display information from
	 */
	public GameState execute(Message message){
		GameState.GameMode mode = message.getGameMode();
		Player currentPlayer = gameState.getCurrentPlayer();
		// debugging purposes
		// System.out.println("GameMode now is: "+gameState.getGameMode() + ". Message is "+mode);
		if (validate(mode)){ // checks whether flow of game is correct
			switch(mode){
			case SPLASH_SCREEN:
				// should not call this, but if they do, turn to NEW_GAME mode
				gameState.setGameMode(GameState.GameMode.NEW_GAME);
				break;
			case NEW_GAME:
				gameState.resetEverything();
				AINumber = 0;
				ArrayList<Building> allBuildingsAvailableForBuilding = new ArrayList<Building>();
				for (Building b : gameState.getBuildingManager().getManager()){
					allBuildingsAvailableForBuilding.add(b);
				}
				gameState.setAllBuildingsAvailableForBuilding(allBuildingsAvailableForBuilding);
				if (isSet){
					gameState.setTileManager(new TileManager(setTileManagerPath));
				}
				gameState.setGameMode(GameState.GameMode.NEW_GAME);
				break;
			case INITIALIZE:
				String[] playerlist = message.getArgs();
				int noOfPlayers = 0;
				for (int i = 0; i < (playerlist.length - 1); i += 2){
					if (playerlist[i] != null && (!playerlist[i].equals("None"))){
						noOfPlayers++;
					}
				}
				if (playerlist.length >= 9 && StringToAIDifficulty(playerlist[8]) != null){
					gameState.setDifficultyLevel(StringToAIDifficulty(playerlist[8]));
				}
				int[] homebases = getHomeBases(noOfPlayers);
				int j = 0;
				for (int i = 0; i < (playerlist.length - 1); i += 2){
					if (playerlist[i] != null && playerlist[i].equals("Human")){
						Player player = new Player(playerlist[i + 1], homebases[j++]);
						gameState.addPlayer(player);
					} else if (playerlist[i] != null && playerlist[i].equals("AI")){
						Player player = null;
						switch (gameState.getDifficultyLevel()) {
						case EASY:
							player = (Player) new YujingAI(getAIName(), homebases[j++]);
							break;
						case NORMAL:
							player = (Player) new VincentAI(getAIName(), homebases[j++], VincentAI.Mode.NORMAL);
							break;
						case HARD:
							player = (Player) new VincentAI(getAIName(), homebases[j++], VincentAI.Mode.HARD);
							break;
						default:
							break;
						}
						gameState.addPlayer(player);
					}
				}
				currentPlayer = gameState.getCurrentPlayer();
				gameState.setPrevOutput("It is "+currentPlayer.getName()+"'s turn now.");
				gameState.setPrevGameMode(GameState.GameMode.ROLL_DIE);
				gameState.setGameMode(GameState.GameMode.INITIALIZE);
				break;
			case ROLL_DIE:
				int dieRoll = currentPlayer.throwDie();
				rollDie(dieRoll);
				if (getAI(currentPlayer) != null){
					gameState.setGameMode(GameState.GameMode.MOVE_PIECE);
					moveAI();
				} else {
					gameState.setGameMode(GameState.GameMode.MOVE_PIECE);
				}
				break;
			case SET_DIE_ROLL: // ATD will call this instead
				rollDie(Integer.decode(message.getArgs()[0]));
				gameState.setGameMode(GameState.GameMode.MOVE_PIECE);
				break;
			case MOVE_PIECE:
				Integer tileNumber = null;
				String[] args = message.getArgs();
				if (args[0] == null || !isInteger(args[0])){
					gameState.setPrevOutput(gameState.getOutput());
					gameState.setPrevGameMode(gameState.getGameMode());
					gameState.setGameMode(GameState.GameMode.ERROR);
					gameState.setOutput("Cannot return such a tile number");
				} else {
					tileNumber = Integer.decode(args[0]);
					boolean validTileNumber = false;
					for (Integer i : gameState.getTilesYouCanMoveTo()){
						if (i == tileNumber){
							validTileNumber = true;
						}
					}
					if (!validTileNumber){
						gameState.setPrevOutput(gameState.getOutput());
						gameState.setPrevGameMode(gameState.getGameMode());
						gameState.setGameMode(GameState.GameMode.ERROR);
						gameState.setOutput("Cannot return such a tile number");
					} else {
						if (tileNumber == currentPlayer.getHomeBase()){
							currentPlayer.changePiecePosition(tileNumber);
							initializeBuild();
							gameState.setGameMode(GameState.GameMode.BUILD);
						} else {
							tileAction(tileNumber);
							gameState.setGameMode(GameState.GameMode.TILE_RESULTS);
						}
					}
				}
				break;
			case TILE_RESULTS:
				gameState.setOutput("Your turn has ended.");
				gameState.setGameMode(GameState.GameMode.END_TURN);
				break;
			case BUILD:
				if (message.getArgs() == null){
					// no building were selected
					gameState.setOutput("You did not build anything. Your turn has ended.");
					gameState.setGameMode(GameState.GameMode.END_TURN);
				} else {
					String buildingName = message.getArgs()[0];
					if (buildingName == null || gameState.getBuildingManager().stringToBuilding(buildingName) == null){
						gameState.setPrevOutput(gameState.getOutput());
						gameState.setPrevGameMode(gameState.getGameMode());
						gameState.setGameMode(GameState.GameMode.ERROR);
						gameState.setOutput("Such a building does not exist");
					} else {
						Building building = gameState.getBuildingManager().stringToBuilding(buildingName);
						addBuildingToPlayer(currentPlayer, building);
						if (gameState.hasWon()){
							gameState.setOutput("You have built a " + building.getName() + ".<br>You have achieved the minimum conditions to win the game (game will end in this round). Your turn has ended.");
						} else {
							gameState.setOutput("You have built a " + building.getName() + ".<br>Your turn has ended.");
						}
						gameState.setGameMode(GameState.GameMode.END_TURN);	
					}
				}
				break;
			case END_TURN:
				if (gameState.hasWon() && gameState.lastPlayer()){
					gameState.setGameMode(GameState.GameMode.END_GAME);
					ArrayList<Player> winnerList = gameState.getWinners();
					if (winnerList.size() > 1){
						Boolean needComma = false;
						String output = "";
						for (Player p : winnerList){
							if (needComma){
								output += ", ";
							}
							output += p.getName();
							needComma = true;
						}
						output += " are the WINNERS!";
						gameState.setOutput(output);
					} else {
						gameState.setOutput(gameState.getWinners().get(0).getName() + " is the WINNER!");
					}
				} else {
					gameState.setNextPlayer();
					currentPlayer = gameState.getCurrentPlayer();
					gameState.setOutput("It is "+currentPlayer.getName()+"'s turn now.");
					gameState.setGameMode(GameState.GameMode.ROLL_DIE);
				}
				break;
			case END_GAME:
				gameState.setGameMode(GameState.GameMode.NEW_GAME);
				break;
			case SAVE_GAME:
				if (message.getArgs() == null || message.getArgs()[0] == null){
					gameState.setPrevOutput(gameState.getOutput());
					gameState.setPrevGameMode(gameState.getGameMode());
					gameState.setGameMode(GameState.GameMode.ERROR);
					gameState.setOutput("Please specify a valid save game file");
				} else {
					saveGame(message.getArgs()[0]);
					gameState.setPrevGameMode(gameState.getGameMode());
					gameState.setPrevOutput(gameState.getOutput());
					gameState.setGameMode(GameState.GameMode.SAVE_GAME);
					gameState.setOutput("Game has been saved.");
				}
				break;
			case LOAD_GAME:
				// Debugging
				// System.out.println("Load game called");
				GameState temp = gameState; // keep a gameState in case of failure to load
				gameState = new GameState();
				gameState.resetEverything();
				if (message.getArgs() == null || message.getArgs()[0] == null || (!loadGame(message.getArgs()[0]))){
					gameState = temp;
					gameState.setPrevGameMode(gameState.getGameMode());
					gameState.setPrevOutput(gameState.getOutput());
					gameState.setGameMode(GameState.GameMode.ERROR);
					gameState.setOutput("Something went wrong with loading the file.");
				} else {
					gameState.setPrevGameMode(gameState.getGameMode());
					gameState.setPrevOutput(gameState.getOutput());
					gameState.setGameMode(GameState.GameMode.LOAD_GAME);
					gameState.setOutput("Game was loaded successfully");
				}
				break;
			case RESUME: 
				// after ERROR/SAVE_GAME/INITIALIZE/LOAD_GAME, game should pass back RESUME
				gameState.setGameMode(gameState.getPrevGameMode());
				gameState.setOutput(gameState.getPrevOutput());
				gameState.setPrevGameMode(null);
				gameState.setPrevOutput(null);
				break;
			case ERROR:
				// not supposed to pass this in, but does the same thing as RESUME.
				gameState.setGameMode(gameState.getPrevGameMode());
				gameState.setOutput(gameState.getPrevOutput());
				gameState.setPrevGameMode(null);
				gameState.setPrevOutput(null);
				break;
			default:
				gameState.setPrevGameMode(gameState.getGameMode());
				gameState.setPrevOutput(gameState.getOutput());
				gameState.setGameMode(GameState.GameMode.ERROR);
				gameState.setOutput("Null message passed in");
				break;
			}
		} else {
			// System.out.println(gameState.getGameMode());
			// System.out.println(mode);
			System.err.println("Error in modes that are being passed in");
		}

		return gameState;
	}

	/**
	 * This method calculates the tiles that a certain player can move to, based on the dieRoll
	 * @param dieRoll	The dieRoll of the Player. Determines the tiles player can move to as well as the movement activity points available
	 */
	private void rollDie(int dieRoll) {
		Player currentPlayer = gameState.getCurrentPlayer();
		int from = currentPlayer.getPiecePosition();
		gameState.setMovementActivityPoints(dieRoll + 1); // + 1 to ensure that they get an activity point at least
		ArrayList<Integer> tilesYouCanMoveTo = new ArrayList<Integer>();
		for (int i = 0; i < dieRoll; i++){
			int j = (from + i + 1) % 36;
			if (j != currentPlayer.getHomeBase()){
				tilesYouCanMoveTo.add((Integer) j);
			} else {
				tilesYouCanMoveTo.add((Integer) j);
				break;
			}
		}
		gameState.setTilesYouCanMoveTo(tilesYouCanMoveTo);
		String output = "You have rolled a "+dieRoll+".";
		gameState.setOutput(output);
	}

	/**
	 * This method calculates the activity points left and applies them (Player is assumed to have landed on a normal tile here)
	 * 
	 * @param tileNumber  the tileNumber that player wants to move to. This number is checked in execute() to ensure that it is not the homebase number of the player
	 */
	private void tileAction(Integer tileNumber) {
		Player currentPlayer = gameState.getCurrentPlayer();
		int movementPointsUsed;
		if (tileNumber > currentPlayer.getPiecePosition()){
			movementPointsUsed = tileNumber - currentPlayer.getPiecePosition();
		} else {
			movementPointsUsed = tileNumber + 36 - currentPlayer.getPiecePosition();
		}
		int activityPointsLeft = gameState.getMovementActivityPoints() - movementPointsUsed;
		gameState.setMovementActivityPoints(activityPointsLeft);
		currentPlayer.changePiecePosition(tileNumber);
		Tile tile = gameState.getTileManager().squareToTile(tileNumber);
		TileResult tileResult = tile.execute(gameState.getMovementActivityPoints());
		String story = tileResult.getTileDescription();
		gameState.setOutput(story);
		if (!tileResult.getIsHomeBase()){
			currentPlayer.setCurrentResourceAmount(tileResult.getResourceThatIsAffected(), (currentPlayer.getCurrentResourceAmount(tileResult.getResourceThatIsAffected()) + tileResult.getAmountToIncrease())); 
		}
	}

	/**
	 * This is a helper method that is created so as to initialise the build state.
	 */
	private void initializeBuild() {
		Player currentPlayer = gameState.getCurrentPlayer();
		ArrayList<Building> buildingsYouCanBuild = new ArrayList<Building>();
		ArrayList<Building> allBuildingsAvailableForBuilding = new ArrayList<Building>();

		for (Building b : gameState.getBuildingManager().getManager()){
			allBuildingsAvailableForBuilding.add(b);
			boolean canBuild = true;
			for (BuildingRequirement r : b.getBuildingRequirementList()){
				if (currentPlayer.getCurrentResourceAmount(r.getResources()) < r.getAmount()){
					canBuild = false;
				}
			}
			if (canBuild && (b.getNumberOfBuildingsAvailable() > 0)){
				buildingsYouCanBuild.add(b);
			}
		}		
		gameState.setAllBuildingsAvailableForBuilding(allBuildingsAvailableForBuilding);
		gameState.setBuildingsYouCanBuild(buildingsYouCanBuild);
	}

	/**
	 * Helper method for adding Buildings. Logic to check that Player can build that building first (in execute()) before calling this.
	 * This method tries to ensure that whenever a building is added, the building's quota drops and the player's resources are decreased.
	 * 
	 * @param p the Player who wants to build the building
	 * @param b the building to be added to the Player p
	 */
	private void addBuildingToPlayer (Player p, Building b){
		p.addBuilding(b);
		b.decreaseNumberOfBuildingsAvailable();
		for (BuildingRequirement r : b.getBuildingRequirementList()){
			p.setCurrentResourceAmount(r.getResources(), (p.getCurrentResourceAmount(r.getResources()) - r.getAmount()));
		}
	}

	/**
	 * Helper function to calculate just exactly which are the homebases depending on the number of players
	 * @param noOfPlayers no of players involved in the game
	 * @return a int[] containing the homebases of the players
	 */
	private int[] getHomeBases(int noOfPlayers) {
		switch (noOfPlayers){
		case 0:
		case 1:
			int[] array = {0};
			return array;
		case 2:
			int[] array1 = {0, 18};
			return array1;
		case 3:
			int[] array2 = {0, 9, 18};
			return array2;
		case 4:
			int[] array3 = {0, 9, 18, 27};
			return array3;
		}
		return null;
	}

	/**
	 * This method saves the game
	 * @param nameOfSaveGameFile  the file to write to. Will be created if it does not exist, or overwritten
	 */
	private void saveGame(String nameOfSaveGameFile) {
		String absolutePathOfSaveGame = nameOfSaveGameFile;
		String saveGameState = produceSaveGame();

		try {
			BufferedWriter out = new BufferedWriter(new FileWriter(absolutePathOfSaveGame));
			out.write(saveGameState);
			out.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Helper function to save game. Game is "encrypted" after this
	 * @return a String that will be written into the file
	 */
	private String produceSaveGame() {
		String savedGame = "";
		Boolean needsPartition = false;
		savedGame += gameState.getGameMode() + "\n";
		savedGame += gameState.getPrevGameMode() + "\n";
		savedGame += gameState.getOutput() + "\n";
		savedGame += gameState.getPrevOutput() + "\n";
		savedGame += gameState.getDifficultyLevel() + "\n";
		for (Integer i : gameState.getTilesYouCanMoveTo()){
			if (needsPartition){
				savedGame += " ";
				needsPartition = false;
			}
			savedGame += i;
			needsPartition = true;
		}
		savedGame += "\n";
		needsPartition = false;
		savedGame += gameState.getMovementActivityPoints() + "\n";
		for (Building b : gameState.getBuildingsYouCanBuild()){
			if (needsPartition){
				savedGame += ", ";
				needsPartition = false;
			}
			savedGame += b.getName();
			needsPartition = true;
		}
		savedGame += "\n";
		needsPartition = false;
		for (Building b : gameState.getAllBuildingsAvailableForBuilding()){
			if (needsPartition){
				savedGame += ", ";
				needsPartition = false;
			}
			savedGame += b.getName();
			needsPartition = true;
		}
		savedGame += "\n";
		needsPartition = false;
		Player currentPlayer = gameState.getCurrentPlayer();
		if (currentPlayer != null){
			savedGame += gameState.getPlayerNumber(currentPlayer) + "\n";
		} else {
			savedGame += "\n";
		}
		Boolean needsNewLine = false;
		for (Player p : gameState.getPlayers()){
			if (needsNewLine){
				savedGame += "\n";
				needsNewLine = false;
			}
			savedGame += p.getName() + "\n";
			if (getAI(p) != null){
				savedGame += "AI\n";
			} else {
				savedGame += "\n";
			}
			savedGame += p.getHomeBase() + " " + p.getPointsAccumulated() + " "+ p.getPiecePosition() + "\n";

			for (Building b : p.getBuildingsOwned()){
				if (needsPartition){
					savedGame += ", ";
					needsPartition = false;
				}
				savedGame += b.getName();
				needsPartition = true;
			}
			savedGame += "\n";
			needsPartition = false;
			for (Player.Resource r : Player.Resource.values()){
				if (needsPartition){
					savedGame += " ";
					needsPartition = false;
				}
				savedGame += p.getCurrentResourceAmount(r);
				needsPartition = true;
			}
			needsPartition = false;
			needsNewLine = true;
		}
		needsNewLine = false;
		needsPartition = false;

		savedGame += "\n" + PARTITION_STRING + "\n";
		savedGame += gameState.getTileManager().getTilesData();
		String output = "";
		for (int i = 0; i < savedGame.length(); i++) {
			Character c = savedGame.charAt(i);
			int charValue = c.charValue();
			output += charValue + " ";
		}

		return output;
	}

	/**
	 * This will load a file
	 * @param nameOfSaveGameFile the file to be read from, will return false if it does not exist
	 * @return	false if the file does not follow the format of our save game files
	 */
	private boolean loadGame(String nameOfSaveGameFile) {
		String absolutePathOfSaveGame = nameOfSaveGameFile;

		File savedGame = new File(absolutePathOfSaveGame);

		try {
			FileReader fileSavedGameReader = new FileReader(savedGame);
			BufferedReader saveGameBR = new BufferedReader (fileSavedGameReader);
			String[] saveGameVariables = new String[MAX_FILE_SIZE]; // saveGameVariables[i] will never be null, will always be "" at least
			String loadGameData = "";
			String nextLine = "";
			String tileData = "";
			int i = 0;

			try {
				while ((nextLine = saveGameBR.readLine()) != null){
					loadGameData += nextLine;
				}
				String[] breakup = loadGameData.split(" ");
				String data = "";
				for (String s : breakup){
					if (!isInteger(s)){
						return false;
					} else {
						Character c = (char) (int) Integer.decode(s);
						data += c.toString();
					}
				}

				breakup = data.split("\n");
				boolean readTileData = false;
				for (String s : breakup){
					if (! readTileData){
						if (s.equals(PARTITION_STRING)){
							readTileData = true;
						} else {
							saveGameVariables[i] = s;
							i++;
						}
					} else {
						tileData += s;
					}
				}
				saveGameBR.close();
			} catch (IOException e) {
				return false;
			}

			if (hasStuffInside(saveGameVariables[SAVED_GAME_LINES.GAME_MODE.ordinal()]) && StringToGameMode(saveGameVariables[SAVED_GAME_LINES.GAME_MODE.ordinal()]) == null){
				return false;
			}
			gameState.setGameMode(StringToGameMode(saveGameVariables[SAVED_GAME_LINES.GAME_MODE.ordinal()]));
			if (hasStuffInside(saveGameVariables[SAVED_GAME_LINES.PREV_GAME_MODE.ordinal()]) && StringToGameMode(saveGameVariables[SAVED_GAME_LINES.PREV_GAME_MODE.ordinal()]) == null ){
				return false;
			} else if (hasStuffInside(saveGameVariables[SAVED_GAME_LINES.PREV_GAME_MODE.ordinal()])){
				gameState.setPrevGameMode(StringToGameMode(saveGameVariables[SAVED_GAME_LINES.PREV_GAME_MODE.ordinal()]));
			}

			gameState.setOutput(saveGameVariables[SAVED_GAME_LINES.OUTPUT.ordinal()]);
			gameState.setPrevOutput(saveGameVariables[SAVED_GAME_LINES.PREV_OUTPUT.ordinal()]);
			if (hasStuffInside(saveGameVariables[SAVED_GAME_LINES.DIFFICULTY_LEVEL.ordinal()]) && StringToAIDifficulty(saveGameVariables[SAVED_GAME_LINES.DIFFICULTY_LEVEL.ordinal()]) == null){
				return false;
			} else if (hasStuffInside(saveGameVariables[SAVED_GAME_LINES.DIFFICULTY_LEVEL.ordinal()])){
				gameState.setDifficultyLevel(StringToAIDifficulty(saveGameVariables[SAVED_GAME_LINES.DIFFICULTY_LEVEL.ordinal()]));
			}

			String[] temp = saveGameVariables[SAVED_GAME_LINES.TILES_YOU_CAN_MOVE_TO.ordinal()].split(" ");
			ArrayList<Integer> tilesYouCanMoveTo = new ArrayList<Integer>();
			for (String s : temp){
				if (s.equals("")){
					// ignore
				} else if (! isInteger(s)) {
					return false;
				} else {
					tilesYouCanMoveTo.add(Integer.decode(s));
				}
			}
			gameState.setTilesYouCanMoveTo(tilesYouCanMoveTo);

			if (hasStuffInside(saveGameVariables[SAVED_GAME_LINES.MOVEMENT_ACTIVITY_POINTS.ordinal()]) && (!(isInteger(saveGameVariables[SAVED_GAME_LINES.MOVEMENT_ACTIVITY_POINTS.ordinal()])))){
				return false;
			} else if (isInteger(saveGameVariables[SAVED_GAME_LINES.MOVEMENT_ACTIVITY_POINTS.ordinal()])){
				gameState.setMovementActivityPoints(Integer.decode(saveGameVariables[SAVED_GAME_LINES.MOVEMENT_ACTIVITY_POINTS.ordinal()]));
			}
			temp = saveGameVariables[SAVED_GAME_LINES.BUILDINGS_YOU_CAN_BUILD.ordinal()].split(", ");
			ArrayList<Building> buildingsYouCanBuild = new ArrayList<Building>();
			if (temp != null && temp[0] != null && !(temp[0].equals(""))){
				for (String s : temp){
					Building building = gameState.getBuildingManager().stringToBuilding(s);
					if (building != null){
						buildingsYouCanBuild.add(building);
					} else {
						return false;
					}
				}
			}
			gameState.setBuildingsYouCanBuild(buildingsYouCanBuild);

			temp = saveGameVariables[SAVED_GAME_LINES.ALL_BUILDINGS_AVAILABLE_FOR_BUILDING.ordinal()].split(", ");
			ArrayList<Building> allBuildingsAvailableForBuilding = new ArrayList<Building>();
			if  (temp != null && temp[0] != null && !(temp[0].equals(""))){
				for (String s : temp){
					Building building = gameState.getBuildingManager().stringToBuilding(s);
					if (building != null){
						allBuildingsAvailableForBuilding.add(building);
					} else {
						return false;
					}
				}
			}
			gameState.setAllBuildingsAvailableForBuilding(allBuildingsAvailableForBuilding);

			ArrayList<Player> players = new ArrayList<Player>();
			int nextPlayer = SAVED_GAME_LINES.PLAYER_START_LINE.ordinal();
			while (hasStuffInside(saveGameVariables[nextPlayer])){	
				String name, type;
				int homebase, pointsAccumulated, piecePosition;
				name = saveGameVariables[nextPlayer + PLAYER_GAME_LINES.NAME.ordinal()];
				type = saveGameVariables[nextPlayer + PLAYER_GAME_LINES.TYPE.ordinal()];
				if (type.equals("AI") && (gameState.getDifficultyLevel() == null)){
					return false;
				}
				temp = saveGameVariables[nextPlayer + PLAYER_GAME_LINES.PLAYER_DETAILS.ordinal()].split(" ");
				if (temp[PLAYER_DETAILS.HOMEBASE.ordinal()] != null && isInteger(temp[PLAYER_DETAILS.HOMEBASE.ordinal()])){
					homebase = Integer.decode(temp[PLAYER_DETAILS.HOMEBASE.ordinal()]);
				} else {
					return false;
				}

				if (temp[PLAYER_DETAILS.POINTS.ordinal()] != null && isInteger(temp[PLAYER_DETAILS.POINTS.ordinal()])){
					pointsAccumulated = Integer.decode(temp[PLAYER_DETAILS.POINTS.ordinal()]);
				} else {
					return false;
				}

				if (temp[PLAYER_DETAILS.PIECE_POSITION.ordinal()] != null && isInteger(temp[PLAYER_DETAILS.PIECE_POSITION.ordinal()])){
					piecePosition = Integer.decode(temp[PLAYER_DETAILS.PIECE_POSITION.ordinal()]);
				} else {
					return false;
				}

				Player player = null;
				if (type.equals("AI")){
					switch (gameState.getDifficultyLevel()) {
					case EASY:
						player = (Player) new YujingAI(name, homebase);
						break;
					case NORMAL:
						player = (Player) new VincentAI(name, homebase, VincentAI.Mode.NORMAL);
						break;
					case HARD:
						player = (Player) new VincentAI(name, homebase, VincentAI.Mode.HARD);
						break;
					default:
						return false;
					}
				} else {
					player = new Player(name, homebase);
				}
				player.changePiecePosition(piecePosition);

				// player's building(s)
				temp = saveGameVariables[nextPlayer + PLAYER_GAME_LINES.BUILDING.ordinal()].split(", ");
				if  (temp != null && temp[0] != null && !(temp[0].equals(""))){
					for (String s : temp){
						Building building = gameState.getBuildingManager().stringToBuilding(s);
						if (building != null){
							addBuildingToPlayer(player, building);
						} else {
							return false;
						}
					}
				}

				if (pointsAccumulated != player.getPointsAccumulated()) {
					return false;
				}

				temp = saveGameVariables[nextPlayer + PLAYER_GAME_LINES.RESOURCES.ordinal()].split(" ");
				if (temp.length != RESOURCE_TYPE){
					return false;
				} else {
					for (Player.Resource r : Player.Resource.values()){
						if (! isInteger(temp[r.ordinal()])){
							return false;
						}
						player.setCurrentResourceAmount(r, Integer.decode(temp[r.ordinal()]));
					}
				}
				players.add(player);

				nextPlayer += PLAYER_GAME_LINES.values().length;
				if (nextPlayer >= MAX_FILE_SIZE){
					break;
				}
			}
			gameState.setPlayers(players);

			if (! isInteger(saveGameVariables[SAVED_GAME_LINES.CURRENT_PLAYER.ordinal()])){
				return false;
			} else {
				Integer currentPlayerNumber = Integer.decode(saveGameVariables[SAVED_GAME_LINES.CURRENT_PLAYER.ordinal()]);
				if (currentPlayerNumber >= 0 && currentPlayerNumber < players.size()){
					gameState.setCurrentPlayer(currentPlayerNumber);
				} else {
					return false;
				}
			}
			gameState.getTileManager().makeTiles(tileData);
		} catch (FileNotFoundException e) {
			return false;
		}
		return true;

	}

	/**
	 * Helper function for loading game
	 * @param s	String to check for
	 * @return false if it is either null, equal to "" or "null"
	 */
	private boolean hasStuffInside(String s){
		if (s == null || s.equals("") || s.equals("null")){
			return false;
		} return true;
	}

	/**
	 * Helps in translating Strings to actual GameMode
	 * @param gameMode
	 * @return		the gameMode if it can be matched, null otherwise
	 */
	private GameState.GameMode StringToGameMode(String gameMode){
		if (gameMode == null){
			return null;
		} else if (gameMode.equals("SPLASH_SCREEN")){
			return GameState.GameMode.SPLASH_SCREEN;
		} else if (gameMode.equals("NEW_GAME")){
			return GameState.GameMode.NEW_GAME;
		} else if (gameMode.equals("INITIALIZE")){
			return GameState.GameMode.INITIALIZE;
		} else if (gameMode.equals("ROLL_DIE")){
			return GameState.GameMode.ROLL_DIE;
		} else if (gameMode.equals("SET_DIE_ROLL")){
			return GameState.GameMode.SET_DIE_ROLL;
		} else if (gameMode.equals("MOVE_PIECE")){
			return GameState.GameMode.MOVE_PIECE;
		} else if (gameMode.equals("TILE_RESULTS")){
			return GameState.GameMode.TILE_RESULTS;
		} else if (gameMode.equals("END_TURN")){
			return GameState.GameMode.END_TURN;
		} else if (gameMode.equals("BUILD")){
			return GameState.GameMode.BUILD;
		} else if (gameMode.equals("END_GAME")){
			return GameState.GameMode.END_GAME;
		} else if (gameMode.equals("LOAD_GAME")){
			return GameState.GameMode.LOAD_GAME;
		} else if (gameMode.equals("SAVE_GAME")){
			return GameState.GameMode.SAVE_GAME;
		} else if (gameMode.equals("RESUME")){
			return GameState.GameMode.RESUME;
		} else if (gameMode.equals("ERROR")){
			return GameState.GameMode.ERROR;
		}
		return null;
	}

	/**
	 * Helps in translating Strings to actual AIDifficulty level
	 * @param AIDifficulty
	 * @return			the actual AIDifficulty, or null if it cannot be found
	 */
	private GameState.AIDifficulty StringToAIDifficulty(String AIDifficulty){
		if (AIDifficulty == null){
			return null;
		} else if (AIDifficulty.equals("EASY")){
			return GameState.AIDifficulty.EASY;
		} else if (AIDifficulty.equals("NORMAL")){
			return GameState.AIDifficulty.NORMAL;
		} else if (AIDifficulty.equals("HARD")){
			return GameState.AIDifficulty.HARD;
		}
		return null;
	}

	/**
	 * Helper to check whether a String is an Integer or not
	 * @param s
	 * @return true if it is an Integer, false otherwise
	 */
	private boolean isInteger(String s){
		try {
			Integer.decode(s);
		} catch (NumberFormatException e) {
			return false;
		}
		return true;
	}

	/**
	 * Helper to get different AINames
	 * @return a Different AI Name for each AI in a single game.
	 */
	private String getAIName(){
		AINumber++;
		switch (AINumber) {
		case 1:
			return "First AI";
		case 2:
			return "Second AI";
		case 3:
			return "Third AI";
		case 4:
			return "Fourth AI";
		} 
		AINumber = 0; // automatic reset if AINumber > 4  or < 0
		return getAIName();
	}

	/**
	 * tries to cast a Player into an AI
	 * @param player
	 * @return			AI if the Player is an AI, otherwise null
	 */
	public AI getAI(Player player){
		AI newAI;
		try {
			newAI = (AI) player;
			return newAI;
		} catch (ClassCastException e){
			return null;
		}
	}

	/**
	 * Can only call this when currentPlayer is an AI
	 * 
	 * Helps to move the AI automatically without player control
	 */
	private void moveAI() {
		String output = "";
		AI theAI = (AI) gameState.getCurrentPlayer();
		int tile = (theAI.selectTile(gameState) % 36); 
		Tile tileToMoveTo = gameState.getTileManager().squareToTile(tile);
		output += theAI.getName() + " has moved to "+tileToMoveTo.getNameOfTile() + ".";
		String[] args = {("" + tile)};
		execute(new Message(GameState.GameMode.MOVE_PIECE, args));
		if (gameState.getGameMode().compareTo(GameState.GameMode.BUILD) == 0){
			Building building = theAI.selectBuilding(gameState);
			if (building == null){
				output += " " + theAI.getName() + " did not build anything.";
			} else {
				addBuildingToPlayer(theAI, building);
				output += " " + theAI.getName() + " built a " + building.getName() + ".";
			}
		} else if (gameState.getGameMode().compareTo(GameState.GameMode.TILE_RESULTS) == 0){
			output += " " + gameState.getOutput() + ".";
		} 
		gameState.setGameMode(GameState.GameMode.END_TURN);
		execute(new Message(GameState.GameMode.END_TURN, null));
		output += " " + gameState.getOutput();
		gameState.setOutput(output);
	}

	/**
	 * Method controls game flow
	 * @param m  the Message's GameMode
	 * @return true if the Message can proceed, false otherwise
	 */
	private boolean validate(GameState.GameMode m){
		switch (gameState.getGameMode()){
		case SPLASH_SCREEN:
			return (m == GameState.GameMode.NEW_GAME || m == GameState.GameMode.LOAD_GAME); // can only call SAVE_GAME and LOAD_GAME from SPLASH_SCREEN
		case ERROR:
		case LOAD_GAME:
		case SAVE_GAME:
		case INITIALIZE:
			return (m == GameState.GameMode.RESUME);
		}
		ArrayList<GameState.GameMode> acceptableList = new ArrayList<GameState.GameMode>();
		acceptableList.add(GameState.GameMode.NEW_GAME); // these are equivalent to "interrupts" and they may be called anytime
		acceptableList.add(GameState.GameMode.LOAD_GAME);
		acceptableList.add(GameState.GameMode.SAVE_GAME);
		switch (gameState.getGameMode()){
		case NEW_GAME:
			acceptableList.add(GameState.GameMode.INITIALIZE);
			break;
		case ROLL_DIE:
			acceptableList.add(gameState.getGameMode());
			acceptableList.add(GameState.GameMode.SET_DIE_ROLL);
			break;
		case TILE_RESULTS:
			acceptableList.add(GameState.GameMode.END_TURN); // actual implementation jumps from tile_results to end_turn
		case MOVE_PIECE:
		case BUILD:
		case END_TURN:
		case END_GAME:
			acceptableList.add(gameState.getGameMode());
			break;
		}
		return (acceptableList.contains(m));
	}

	/**
	 * Method to be called by ATD
	 * @param tileManager  fileName of the defaultTiles format
	 */
	public void setTileManager(String tileManager) {
		isSet = true;
		setTileManagerPath = tileManager;
	}

	/*public GameState clone(GameState oldGameState){
		GameState newGameState = new GameState();
		newGameState.setGameMode(oldGameState.getGameMode());
		newGameState.setPrevGameMode(oldGameState.getPrevGameMode());
		newGameState.setOutput(oldGameState.getOutput());
		newGameState.setDifficultyLevel(oldGameState.getDifficultyLevel());
		newGameState.setPrevOutput(oldGameState.getPrevOutput());
		newGameState.setDifficultyLevel(oldGameState.getDifficultyLevel());
		ArrayList<Player> newPlayers = new ArrayList<Player>();
		for (Player p : oldGameState.getPlayers()){
			//TODO:
			// "cloning of Player" but pieces file names not cloned over
			Player newp = null;
			if (getAI(p) != null){
				switch (newGameState.getDifficultyLevel()){
				case EASY:
					newp = new YujingAI(p.getName(), p.getHomeBase());
					break;
				case NORMAL:
					newp = new VincentAI(p.getName(), p.getHomeBase(), VincentAI.Mode.NORMAL);
					break;
				case HARD:
					newp = new VincentAI(p.getName(), p.getHomeBase(), VincentAI.Mode.HARD);
					break;
				}
			} else {
				newp = new Player(p.getName(), p.getHomeBase());
			}
			for (Player.Resource r : Player.Resource.values()){
				newp.setCurrentResourceAmount(r, p.getCurrentResourceAmount(r));
			}
			for (Building b : p.getBuildingsOwned()){
				p.addBuilding(b);
			}
			newPlayers.add(p);
		}
		newGameState.setPlayers(newPlayers);
		newGameState.setCurrentPlayer(oldGameState.getPlayerNumber(oldGameState.getCurrentPlayer()));
		ArrayList<Integer> newTilesYouCanMoveTo = new ArrayList<Integer>();
		for (Integer i : oldGameState.getTilesYouCanMoveTo()){
			newTilesYouCanMoveTo.add(i);
		}
		newGameState.setTilesYouCanMoveTo(newTilesYouCanMoveTo);
		newGameState.setMovementActivityPoints(oldGameState.getMovementActivityPoints());
		newGameState.setTileManager(oldGameState.getTileManager());
		newGameState.setBuildingManager(oldGameState.getBuildingManager());
		ArrayList<Building> newBuildingsYouCanBuild = new ArrayList<Building>();
		for (Building b : oldGameState.getBuildingsYouCanBuild()){
			newBuildingsYouCanBuild.add(b);
		}
		newGameState.setBuildingsYouCanBuild(newBuildingsYouCanBuild);
		ArrayList<Building> newAllBuildingsAvailableForBuilding = new ArrayList<Building>();
		for (Building b : oldGameState.getAllBuildingsAvailableForBuilding()){
			newAllBuildingsAvailableForBuilding.add(b);
		}
		newGameState.setAllBuildingsAvailableForBuilding(newAllBuildingsAvailableForBuilding);
		return newGameState;
	}
	 */

	/*public static void main (String[] args){
		Logic logic = new Logic();
		String[] playerlist = {"Human", "Lala","Human", "Bala","Human", "Baba", "None", null};
		logic.execute(new Message(GameState.GameMode.INITIALIZE, playerlist));

		Player lala = (Player) new YujingAI("lala", 50);
		AI newAI = null;
		if (lala.getClass() != null && lala.getClass().equals(YujingAI.class)){
			newAI = new YujingAI(lala.getName(), lala.getHomeBase());
		} else if (lala.getClass() != null && lala.getClass().equals(VincentAI.class)){
			newAI = new VincentAI(lala.getName(), lala.getHomeBase(), VincentAI.Mode.HARD);
		} System.out.println(newAI.getClass());
	}*/
}
